/**
 * Copyright (c) 2009 ben rimbey
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the 'Software'), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 *
 * need help with this, or have an idea as to how we can make it better?
 * please email me: ben.rimbey@gmail.com
 * http://benrimbey.wordpress.com
 */

package org.puremvc.as3.utilities.asyncStartupUtils.patterns.command {

	import flash.utils.describeType;

	import org.puremvc.as3.interfaces.ICommand;
	import org.puremvc.as3.interfaces.INotification;
	import org.puremvc.as3.interfaces.INotifier;
	import org.puremvc.as3.patterns.observer.Notifier;
	import org.puremvc.as3.utilities.asyncStartupUtils.interfaces.IAsyncCommand;

	public class StartupMacroCommand extends Notifier implements ICommand, INotifier {

		internal var commandList:Array = [];
		internal var cachedNotification:INotification;
		internal var executedCommandHash:Object = {};
		internal var logDebugPrefix:String = "";

		protected var logDebug:Boolean = false;


		public function StartupMacroCommand() {
			initializeStartupMacroCommand();
		}

		public function execute(notification:INotification):void {
			commandList = commandList.sortOn("classDependencyCount");

			cachedNotification = notification;
			spoolOffCommand();
		}

		protected function initializeStartupMacroCommand():void {
			//must be overrode, for that is the puremvc way.
		}

		protected function addSubCommand(command:Class, classDependencyList:Array = null):void {
			if (classDependencyList != null && classDependencyList.length > 0) {
				for each (var c:Class in classDependencyList) {
					trackClassDependency(c);
				}
			}

			trackClassDependency(command, classDependencyList);
		}

		internal function trackClassDependency(classRef:Class, dependencyList:Array = null):void {
			var startupVO:StartupCommandVO = retrieveVOByClass(classRef);

			if (startupVO == null) {
				startupVO = new StartupCommandVO();
				startupVO.command = classRef;
				startupVO.commandClassName = describeType(classRef).@name;

				commandList.push(startupVO);
			}

			if (dependencyList != null) {
				startupVO.classDependencyList = startupVO.classDependencyList.concat(dependencyList);
				startupVO.classDependencyCount = dependencyList.length;
			}
		}

		internal function retrieveVOByClass(classRef:Class):StartupCommandVO {
			for each (var vo:StartupCommandVO in commandList) {
				if (vo.command == classRef) {
					return vo;
				}
			}

			return null;
		}

		internal function checkClassAncestory(classRef:Class):void {
			var def:XML = describeType(classRef);
			var name:String = def.@name;
			var found:Boolean = false;

			for each (var node:XML in def.factory.(@type == name).implementsInterface) {
				if (String(node.@type).indexOf("IAsyncCommand") > -1) {
					found = true;
					break;
				}
			}

			if (!found) {
				throw new Error("Class " + name + " must implement IAsyncCommand to be used with AsyncStartupUtils!");
			}
		}

		internal function spoolOffCommand():void {
			var commandInstance:IAsyncCommand = new(chooseNextCommandClass())();
			commandInstance.completeCallBack = handleCommandComplete;

			log("executing: " + commandInstance);
			logDebugPrefix = "";

			commandInstance.execute(cachedNotification);
		}

		internal function chooseNextCommandClass():Class {
			var returnClass:Class;

			if ((commandList[0] as StartupCommandVO).classDependencyCount == 0) {
				returnClass = (commandList.shift() as StartupCommandVO).command;
			} else {
				try {
					var voToRemove:StartupCommandVO = recursivelySelectNextDependency(commandList[0] as StartupCommandVO);
					returnClass = voToRemove.command;
					removeCommandVOFromList(voToRemove);
				} catch (e:Error) {
					throw new Error("Circular dependency found when attempting to execute ==> " + (commandList[0] as StartupCommandVO).command);
				}
			}

			executedCommandHash[returnClass] = 1;
			return returnClass;
		}

		internal function recursivelySelectNextDependency(vo:StartupCommandVO):StartupCommandVO {
			logDebugPrefix += "..";
			log(logDebugPrefix + "checking dependency: " + vo.command);

			for each (var dependency:Class in vo.classDependencyList) {
				if (!executedCommandHash.hasOwnProperty(dependency)) {
					var voToScan:StartupCommandVO = retrieveVOByClass(dependency);
					vo = recursivelySelectNextDependency(voToScan);
				}
			}
			return vo;
		}

		internal function removeCommandVOFromList(vo:StartupCommandVO):void {
			for (var i:int = 0; i < commandList.length; i++) {
				if (vo === (commandList[i] as StartupCommandVO)) {
					commandList.splice(i, 1);
					break;
				}
			}
		}

		internal function handleCommandComplete():void {
			if (commandList.length > 0) {
				spoolOffCommand();
			}
		}

		internal function log(message:String):void {
			if (logDebug) {
				trace(this + " ==> " + message);
			}
		}

	}
}

class StartupCommandVO {
	public var command:Class;
	public var commandClassName:String;
	public var classDependencyList:Array = [];
	public var classDependencyCount:uint = 0;
}